package user

import (
	"encoding/json"
	"sanguo/cluster"
	codecs "sanguo/codec/cs"
	"sanguo/common"
	"sanguo/node/common/asynwrap"
	"sanguo/node/common/db"
	"sanguo/node/node_game"
	cs_message "sanguo/protocol/cs/message"
	ss "sanguo/protocol/ss/message"
	"time"

	"github.com/go-redis/redis"
	"github.com/golang/protobuf/proto"
	"github.com/sniperHW/kendynet"
)

func onLoginError(user *User, session kendynet.StreamSession, code cs_message.EnumType) {
	loginResp := &cs_message.GameLoginToC{
		Code: cs_message.EnumType(code).Enum(),
	}
	session.Send(codecs.NewMessage(0, loginResp))
	session.SetUserData(nil)
	session.Close("login error", time.Second)
	if user != nil {
		if user.checkStatus(status_login2) {
			user.setStatus(status_logout)
			//需要清除redis上的记录
			clearUserGame(user.userID, func() {
				user.setStatus(status_end)
				delete(userMap, user.userID)
			})
		} else {
			delete(userMap, user.userID)
		}
	}
}

func onReconnect(session kendynet.StreamSession, msg *codecs.Message) {
	reconnect := msg.GetData().(*cs_message.ReconnectToS)
	userID := reconnect.GetUserID()
	token := reconnect.GetToken()
	seqNo := msg.GetSeriNo()
	node_game.Debugln("onReconnect:", userID, session.RemoteAddr())
	resp := &cs_message.ReconnectToC{}

	u := userMap[userID]

	if nil == u || (!u.checkStatus(status_reconnect) && u.session != nil) {
		node_game.Debugln(userID, "Reconnect faild 1")
		resp.Code = cs_message.EnumType(cs_message.EnumType_ERROR).Enum()
	} else if !node_game.CheckToken(userID, token) {
		node_game.Debugln(userID, "Reconnect faild 2")
		resp.Code = cs_message.EnumType(cs_message.EnumType_ERROR).Enum()
	} else {
		if u.lastTimer != nil && cluster.UnregisterTimer(u.lastTimer) {
			//心跳
			u.nextCheckHeartBeat = time.Now().Unix() + common.HeartBeat_Timeout_Client
			u.onLine()

			u.setStatus(status_playing)
			resp.Code = cs_message.EnumType(cs_message.EnumType_OK).Enum()
			u.session = session
			session.SetUserData(u)
			node_game.Infoln(userID, "Reconnect ok")
		} else {
			node_game.Debugln(userID, "Reconnect faild 3")
			resp.Code = cs_message.EnumType(cs_message.EnumType_ERROR).Enum()
		}
	}

	session.Send(codecs.NewMessage(seqNo, resp))
}

//登录请求
func onLogin(session kendynet.StreamSession, msg *codecs.Message) {
	loginReq := msg.GetData().(*cs_message.GameLoginToS)
	userID := loginReq.GetUserID()
	token := loginReq.GetToken()
	node_game.Debugln("OnLogin:", userID, session.RemoteAddr())
	if !node_game.CheckToken(userID, token) {
		//令牌校验非法
		node_game.Infoln(userID, "CheckToken failed", token)
		onLoginError(nil, session, cs_message.EnumType_LOGIN_ERROR)
		return
	}
	u := userMap[userID]
	if nil != u {
		if u.checkStatus(status_reconnect) && (u.session == nil) {
			if u.lastTimer != nil && !cluster.UnregisterTimer(u.lastTimer) {
				node_game.Infoln(userID, "login failed 1")
				onLoginError(nil, session, cs_message.EnumType_LOGIN_RETRY)
			} else {
				u.session = session
				u.enterAreaID = loginReq.GetServerID()
				session.SetUserData(u)
				u.loginOK()
			}
		} else if u.checkStatus(status_playing) {
			if session == u.session {
				//重复的登录消息
				node_game.Errorln(userID, "duplicate login req")
			} else {
				//尝试踢人
				u.kickByLogin(session)
				node_game.Infoln(userID, "login failed 2")
				onLoginError(nil, session, cs_message.EnumType_LOGIN_RETRY)
			}
		} else {
			node_game.Infoln(userID, "login failed 3")
			onLoginError(nil, session, cs_message.EnumType_LOGIN_RETRY)
		}
		return
	}

	user := New(session, userID)

	session.SetUserData(user)
	user.enterAreaID = loginReq.GetServerID()
	user.setStatus(status_login1)
	userMap[userID] = user

	//向数据库添加登录标记，如果成功继续后续流程，否则通知客户端重试
	key := userID
	val := node_game.GetSelfService().ToPeerID().ToString()

	setUserStr := `
		local game = redis.call('hget',KEYS[1],ARGV[1])
		if not game then
			redis.call('hset',KEYS[1],ARGV[1],ARGV[2])
			return 'ok'
		elseif game == ARGV[2] then
			return 'ok'
		else
			return game
		end
	`

	keys := []string{}
	keys = append(keys, "game_user_map")

	asynwrap.Wrap(db.GetRedisClient("login").Eval)(func(ret []interface{}) {
		result, err := ret[0].(*redis.Cmd).Result()
		if nil != err {
			node_game.Infoln(userID, "login failed 3")
			onLoginError(user, session, cs_message.EnumType_LOGIN_RETRY)
			return
		}
		if result.(string) == "ok" {
			user.setStatus(status_login2)
			load(user, session)
		} else {
			node_game.Infoln(userID, "login failed 4")
			onLoginError(user, session, cs_message.EnumType_LOGIN_RETRY)
			//向目标服发出踢人请求，并通知玩家重试
			msg := &ss.Kick{
				UserID: proto.String(userID),
			}
			cluster.PostMessage(cluster.PeerID(result.(string)), msg)
		}

	}, setUserStr, keys, key, val)
}

func load(user *User, session kendynet.StreamSession) {

	user.CreateModule("role")
	key := "user:" + user.userID

	asynwrap.Wrap(db.GetRedisClient("game").HGet)(func(ret []interface{}) {
		result, err := ret[0].(*redis.StringCmd).Result()
		if err != nil {
			if err.Error() == "redis: nil" {
				register(user, session)
			} else {
				node_game.Infoln(user.userID, "login failed 5")
				onLoginError(user, session, cs_message.EnumType_LOGIN_ERROR)
			}
		} else {
			err = json.Unmarshal(([]byte)(result), &user.userData)
			if nil != err {
				node_game.Infoln(user.userID, "login failed 6", err.Error(), result)
				onLoginError(user, session, cs_message.EnumType_LOGIN_ERROR)
			} else {
				user.load()
			}
		}
	}, key, "userData")
}

func (this *User) loginOK() {
	if this.checkStatus(status_login2) || this.checkStatus(status_register) || this.checkStatus(status_reconnect) {
		node_game.Infoln(this.userID, " loginOK")
		this.setStatus(status_playing)
		//心跳
		this.nextCheckHeartBeat = time.Now().Unix() + common.HeartBeat_Timeout_Client

		if nil != this.session {
			loginResp := &cs_message.GameLoginToC{
				Code: cs_message.EnumType(cs_message.EnumType_OK).Enum(),
			}
			this.session.Send(codecs.NewMessage(0, loginResp))

		} else {
			this.setStatus(status_reconnect)
			/*
			 *	设置定时器，如果玩家在定时器到期前没有重连上来销毁对象
			 */
			this.lastTimer = cluster.RegisterTimer(time.Now().Add(time.Second*600), this.finalTimeout)
		}
	}
}

func (this *User) onLine() {
	this.GetModuleByName("role").(interface {
		OnLine()
	}).OnLine()
}
